Skip to content

feat(runtime,fs): emit FsChanged for in-shell VFS writes - #1

Open
yshaul wants to merge 1 commit into
mayflower:mainfrom
yshaul:feat/fs-changed-in-shell-writes
Open

feat(runtime,fs): emit FsChanged for in-shell VFS writes#1
yshaul wants to merge 1 commit into
mayflower:mainfrom
yshaul:feat/fs-changed-in-shell-writes

Conversation

@yshaul

@yshaul yshaul commented Jun 18, 2026

Copy link
Copy Markdown

Summary

FsChanged(path) was emitted only by the explicit WriteFile protocol command. In-shell writes — echo > f, touch, mkdir, cp, rm, tee, redirections — ran through the executor and emitted just [Exit], even though the file was really written (read-back confirms). The protocol doc promised notification "when scripts touch the FS", but the standalone/Pyodide paths didn't deliver it.

This adds a single chokepoint at the VFS layer:

  • wasmsh-fs: new FsChangeLog — an Rc<RefCell<…>>-backed, insertion-ordered, de-duplicating set of mutated paths, exposed via a defaulted Vfs::change_log() accessor. MemoryFs and EmscriptenFs record the path on every mutating op (write-intent open, write_file, open_write_sink, create_dir, remove_file, remove_dir). The OpfsFs stub inherits the None default.
  • wasmsh-runtime: drain_fs_change_events emits one FsChanged(path) per distinct path at each event-batch boundary (poll_active_run Done + Yield, finish_idle_signal_exit), placed after I/O/diagnostics and before Exit. Internal scratch paths (/tmp/_wasmsh_*, /tmp/_proc_subst_*) are suppressed. handle_write_file_command is unified onto the same drain path.
  • Docs: docs/reference/protocol.md updated to match; design recorded in ADR-0032 (status: Proposed).

Semantics: per-path, de-duplicated per run, first-touch order. Scope note: files written by the in-process Python interpreter go through Emscripten libc directly (not the shell VFS), so they are not reported via FsChanged.

This is purely additive to the event stream — hosts already handling FsChanged from WriteFile need no changes.

Test plan

Per ADR-0020 (E2E-first); FsChanged isn't expressible in the TOML differential oracle (real bash emits no such event).

  • cargo test -p wasmsh-fs — change-log record/dedup/order/read-only unit tests
  • cargo test -p wasmsh-runtime — protocol tests: redirect, mkdir/touch/cp/rm per-path, dedup, read-only emits nothing, heredoc scratch suppressed
  • cargo fmt --all, cargo clippy (incl. --features emscripten), cargo check --workspace --all-targets
  • TOML suite (wasmsh-testkit suite_runner) green — no regression
  • just test-e2e-standalone (added cases in e2e/standalone/tests/file-ops.spec.ts) — requires wasm-pack build
  • just test-e2e-pyodide-node (added case in e2e/pyodide-node/tests/protocol-parity.test.mjs) — requires emcc/Pyodide build

Made with Cursor

FsChanged was only emitted by the explicit WriteFile protocol command;
in-shell writes (echo > f, touch, mkdir, cp, rm, tee, redirections) ran
through the executor and emitted just [Exit], despite the protocol doc
promising notification when scripts touch the FS.

Add a shared, ordered, de-duplicating FsChangeLog to wasmsh-fs, recorded
by MemoryFs and EmscriptenFs on every mutating op (write-intent open,
write_file, open_write_sink, create_dir, remove_file, remove_dir). The
runtime drains it into one FsChanged(path) per distinct path at each
event-batch boundary, skipping internal scratch paths (/tmp/_wasmsh_*,
/tmp/_proc_subst_*). WriteFile is unified onto the same drain path.

Covered E2E-first per ADR-0020: wasmsh-fs unit tests, runtime protocol
tests (in-shell writes, dedup, read-only, scratch suppression), and E2E
additions for standalone + pyodide-node. Design recorded in ADR-0032.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant